﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Net;
using System.Net.Mail;
using System.Text;
using System.Web;
using System.Web.Services;
using SdmxMl.Multiplexor;

namespace SdmxMailNotifier
{
    /// <summary>
    /// This class implement the sending of the notification to the users
    /// </summary>
    public class Notifier
    {
        #region Constants
        // Constants for the mail text
        private const string NOTIFICATION_TITLE = "SMS changes subscription";
        private const string NOTIFICATION_STARTER = "Statistical Metadata System (SFO) - Artefacts modified since ";
        private const string CREATED_ARTEFACTS_STARTER = "Created Artefacts";
        private const string UPDATED_ARTEFACTS_STARTER = "Updated Artefacts";

        // Constants for the HTML tags
        private const string HTML_NEW_LINE = "<br>";
        private const string HTML_BOLD_START = "<b>";
        private const string HTML_BOLD_END = "</b>";
        private const string HTML_TAB = "<tab>";
        #endregion

        #region (Internal and External) Members
        private SmtpClient mailClient;
        private string mailFrom;
        private string mailFromDisplay;
        #endregion

        #region Constructor
        public Notifier (string smtpServerHost, int smtpServerPort, string user, string passwd, bool enableSSL, string mailFrom, string mailFromDisplay)
        {
            this.mailClient = new SmtpClient(smtpServerHost, smtpServerPort);
            if (user.Trim().Length > 0)
            {
                this.mailClient.Credentials = new NetworkCredential(user, passwd);
            }
            this.mailClient.EnableSsl = enableSSL;
            this.mailClient.DeliveryMethod = SmtpDeliveryMethod.Network;
            this.mailClient.UseDefaultCredentials = false;
            this.mailFrom = mailFrom;
            this.mailFromDisplay = mailFromDisplay;
        }
        #endregion

        #region Internal Notifications Method
        private void generateMailSection (StringBuilder builder, string starter, string announce, string announceDateIndex, 
            Dictionary<string, List<DataRow>> artefacts)
        {
            if (artefacts.Count > 0)
            {
                builder.Append(HTML_BOLD_START).Append(starter).Append(HTML_BOLD_END).Append(HTML_NEW_LINE);
                foreach (string key in artefacts.Keys)
                {
                    builder.Append(key).Append(": ").Append(HTML_NEW_LINE);
                    foreach (DataRow row in artefacts[key])
                    {
                        DateTime announceDate = DateTime.Parse(row[announceDateIndex].ToString());

                        builder.Append(HTML_TAB).Append(row["AI"]).Append("+").Append(row["ID"]).Append("+").Append(row["VER"]).
                                Append(announce).Append(announceDate.ToString("s")).
                                Append(bool.Parse(row["IF"].ToString()) ? " (FINAL)" : "").Append(HTML_NEW_LINE);
                    }
                }
            }
        }

        private void SendNotification(string mailAddress, DateTime lastUpdate, DataSet modifiedArtefacts)
        {
            // Construct the notification from the set of modified artefacts
            StringBuilder mailBody = new StringBuilder();
            mailBody.Append(HTML_BOLD_START).Append(NOTIFICATION_STARTER).Append(lastUpdate.ToString()).Append(HTML_BOLD_END).Append(HTML_NEW_LINE);
            mailBody.Append(HTML_NEW_LINE);

            // Construct the map for updated and created artefacts
            Dictionary<string, List<DataRow>> createdArtefacts = new Dictionary<string, List<DataRow>>();
            Dictionary<string, List<DataRow>> updatedArtefacts = new Dictionary<string, List<DataRow>>();
            foreach (DataTable table in modifiedArtefacts.Tables)
            {
                foreach (DataRow row in table.Rows)
                {
                    Dictionary<string, List<DataRow>> dictionary = DateTime.Parse(row["CR"].ToString()) > lastUpdate ? createdArtefacts : updatedArtefacts;
                    if (!dictionary.ContainsKey(table.TableName))
                    {
                        dictionary[table.TableName] = new List<DataRow>();
                    }
                    dictionary[table.TableName].Add(row);
                }
            }

            // Generate the section for created and updated artefacts
            generateMailSection(mailBody, CREATED_ARTEFACTS_STARTER, " created at ", "CR", createdArtefacts);
            mailBody.Append(HTML_NEW_LINE);
            generateMailSection(mailBody, UPDATED_ARTEFACTS_STARTER, " modified at ", "LU", updatedArtefacts);

            // Construct the mail and send it through the SMTP server
            MailMessage mailMsg = new MailMessage(new MailAddress (this.mailFrom, this.mailFromDisplay), new MailAddress (mailAddress));
            mailMsg.Subject = NOTIFICATION_TITLE;
            mailMsg.Body = mailBody.ToString ();
            mailMsg.IsBodyHtml = true;
            mailClient.Send(mailMsg);
        }

        // Compute the artefacts that has been modified in the specified period
        private DataSet GetModifiedArtefacts(DateTime fromDate, DateTime toDate)
        {
            DataSet result = new DataSet();

            // Iterate on all registries
            // TODO use the interface for multiplexor implemented by the tiny registry instead of the multiplexor
            // TODO also use the column name constants are use methods to access the fields
            RegistryMultiplex multiplexor = new RegistryMultiplex();
            foreach (RegProxy proxy in multiplexor.GetProxies())
            {
                // Retrieve all the artefacts that has been modified during the specified period on the registry
                DataSet proxyResult = proxy.GetArtefactInfos(".*", ".*", ".*", ".*", fromDate, toDate);
                //DataSet proxyResult = proxy.DirInfo("CodeLists");
                foreach (DataTable proxyTable in proxyResult.Tables)
                {
                    // Merge the set of modified artefacts in a single data set
                    DataTable resultTable = result.Tables[proxyTable.TableName];
                    if (resultTable == null)
                    {
                        resultTable = proxyTable.Copy();
                        result.Tables.Add(resultTable);
                    }
                    else
                    {
                        foreach (DataRow proxyRow in proxyTable.Rows)
                        {
                            // TODO ideally if the row is already in the table, an exception should be thrown !
                            // However, in the current implementation, the artefact for user rights share the same name
                            if (resultTable.Rows.Find(new object[] { proxyRow["ID"], proxyRow["VER"], proxyRow["AI"] }) == null)
                            {
                                resultTable.Rows.Add(proxyRow.ItemArray);
                            }
                        }
                    }
                }
            }

            return result;
        }

        private long countArtefacts(DataSet artefacts)
        {
            long result = 0;
            foreach (DataTable table in artefacts.Tables)
            {
                result = result + table.Rows.Count;
            }
            return result;
        }

        // Notify all the specified users about the recently modified artefacts
        public void NotifyUsers(Object usersAsAnObject)
        {
            Trace.TraceInformation("SDMX Notifier starts sending notification at " + DateTime.Now.ToString());
            Trace.Indent();

            if (!(usersAsAnObject is UsersManager))
            {
                throw new ApplicationException("The method NotifyUsers only accept parameter type Users, but current parameter type is "
                    + usersAsAnObject.GetType());
            }
            UsersManager users = (UsersManager)usersAsAnObject;

            DateTime notificationDate = DateTime.Now;
            Dictionary<DateTime, DataSet> modifiedArtefactsSets = new Dictionary<DateTime, DataSet>();

            // Iterate on all users
            foreach (string username in users.GetAllUsers())
            {
                // Load the user 
                User user = users.LoadUser(username, true);
                if (user != null) // Check that the user has not been loaded by another thread
                {
                    try
                    {
                        if (user.MustBeNotified(notificationDate)) // Check that the user expects some notification
                        {
                            DateTime lastUpdate = user.GetLastUpdate();

                            // Compute the recently modified artefacts
                            if (!modifiedArtefactsSets.ContainsKey(lastUpdate))
                            {
                                modifiedArtefactsSets.Add(lastUpdate, GetModifiedArtefacts(lastUpdate, notificationDate));
                            }
                            DataSet modifiedArtefacts = modifiedArtefactsSets[lastUpdate];

                            // Compute the artefacts in which the user is interested
                            DataSet interestingModifiedArtefacts = user.GetInterestingArtefacts(modifiedArtefacts);

                            // Send a notification to the user if some changes occur
                            if (countArtefacts(interestingModifiedArtefacts) > 0)
                            {
                                SendNotification(user.GetMailAddress(), lastUpdate, interestingModifiedArtefacts);
                                user.SignalNotification(notificationDate);
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        Trace.TraceWarning("Cannot send notification to user '" + username + "': " + e.Message);
                    }
                    finally
                    {
                        // Release the user 
                        users.ReleaseUser(user);
                    }
                }
            }

            Trace.Unindent();
            Trace.TraceInformation("SDMX Notifier ends sending notification at " + DateTime.Now.ToString());
        }
        #endregion
    }
}
